package netinst

import (
	"context"
	"fmt"
	"html/template"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"syscall"
)

func InstallDnsmasq(ctx context.Context, name string) error {
	dnsmasqPath, err := exec.LookPath("dnsmasq")
	if err != nil {
		return err
	}

	hostsFile := hostsPath(name)
	if err := createIfNeeded(hostsFile); err != nil {
		return err
	}

	config := dnsmasqConfig{
		Domain:             name + ".local",
		NetworkInterface:   name + "0",
		PidFile:            pidfilePath(name),
		AddnHostsFile:      hostsFile,
		UpstreamResolvFile: upstreamResolvPath,
	}

	dnsmasqConfigFile := dnsmasqConfPath(name)

	if err := writeDnsmasqConfig(dnsmasqConfigFile, config); err != nil {
		return fmt.Errorf("write dnsmasq.conf: %w", err)
	}

	dnsmasq := exec.CommandContext(ctx,
		dnsmasqPath,
		"--keep-in-foreground",
		"--log-facility=-",
		"--log-debug",
		"-u", "root",
		"--conf-file="+dnsmasqConfigFile,
	)

	// forward dnsmasq logs to engine logs for debugging
	dnsmasq.Stdout = os.Stdout
	dnsmasq.Stderr = os.Stderr

	if dnsmasq.SysProcAttr == nil {
		dnsmasq.SysProcAttr = &syscall.SysProcAttr{}
	}
	// If the engine dies, dnsmasq should die too
	dnsmasq.SysProcAttr.Pdeathsig = syscall.SIGKILL
	// Prevent signals forwarded to the engine's process group from automatically going to dnsmasq.
	// The engine handles signals itself and will kill dnsmasq when/if needed. If we don't do this
	// the engine can receive shutdown signals (SIGTERM/SIGINT/etc.), start a graceful shutdown, but
	// dnsmasq will receive those too and exit immediately, not being around for the engine's
	// graceful shutdown.
	dnsmasq.SysProcAttr.Setpgid = true

	// We have to lock proess start+wait to the os thread when using Pdeathsig, otherwise it's
	// possible for the thread that started the process to run elsewhere, exit and the child process
	// to unexpectedly get its parent death signal.
	started := make(chan error, 1)
	go func() {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		if err := dnsmasq.Start(); err != nil {
			started <- fmt.Errorf("start dnsmasq: %w", err)
			close(started)
			return
		}
		close(started)

		err := dnsmasq.Wait()
		if err != nil {
			fmt.Fprintf(os.Stderr, "dnsmasq exited: %v\n", err)
		}
	}()

	select {
	case err := <-started:
		if err != nil {
			return err
		}
	case <-ctx.Done():
		return context.Cause(ctx)
	}

	return nil
}

func writeDnsmasqConfig(dnsmasqConfigFile string, config dnsmasqConfig) error {
	if err := os.MkdirAll(filepath.Dir(dnsmasqConfigFile), 0700); err != nil {
		return err
	}

	conf, err := os.OpenFile(dnsmasqConfigFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return err
	}

	defer conf.Close()

	tmpl, err := template.New("dnsmasq.conf").Parse(dnsmasqTemplate)
	if err != nil {
		return err
	}

	if err := tmpl.Execute(conf, config); err != nil {
		return err
	}

	return conf.Close()
}

const dnsmasqTemplate = `## WARNING: THIS IS AN AUTOGENERATED FILE
## AND SHOULD NOT BE EDITED MANUALLY AS IT
## LIKELY TO AUTOMATICALLY BE REPLACED.
strict-order
local=/{{.Domain}}/
domain={{.Domain}}
domain-needed
expand-hosts
pid-file={{.PidFile}}
except-interface=lo
interface={{.NetworkInterface}}
addn-hosts={{.AddnHostsFile}}
resolv-file={{.UpstreamResolvFile}}
`

type dnsmasqConfig struct {
	Domain             string
	NetworkInterface   string
	PidFile            string
	AddnHostsFile      string
	UpstreamResolvFile string
}
